/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */
#include "TrueType.h"

#include <vector>

#include "resource/Resource.h"
#include "resource/Manager.h"
#include <xml/Stream.h>
#include <Gluck.h>
#include <Color.h>
#include "FontVisitor.h"

#ifndef ANDROID_NDK
#include "TrueText/Error.h"
#endif

#include "TrueText/Font.h"
#include "TrueText/Face.h"
#include "TrueText/Stream.h"
#include "TrueText/FontEffectOutline.h"
#include "TrueText/FontEffectShadow.h"
#include "TrueText/OutlineInfo.h"
#include "TrueText/ShadowInfo.h"
#include <math/Vector2.h>

#include <boost/bind.hpp>

namespace liba {
namespace font {

struct FontInfo
{
	FontInfo()
		: size(0)
		, outline(false)
		, outlineWidth(0.0f)
		, shadow(false)
	{ }

	FontInfo(const FontInfo& rhs)
		: faceFile(rhs.faceFile)
		, size(rhs.size)
		, color(rhs.color)
		, outline(rhs.outline)
		, outlineWidth(rhs.outlineWidth)
		, outlineColor(rhs.outlineColor)
		, shadow(rhs.shadow)
		, shadowOffset(rhs.shadowOffset)
		, shadowColor(rhs.shadowColor)
	{ }

	const FontInfo& operator=(const FontInfo& rhs)
	{
		if (&rhs == this)
			return *this;

		faceFile = rhs.faceFile;
		size = rhs.size;
		color = rhs.color;
		outline = rhs.outline;
		outlineWidth = rhs.outlineWidth;
		outlineColor = rhs.outlineColor;
		shadow = rhs.shadow;
		shadowOffset = rhs.shadowOffset;
		shadowColor = rhs.shadowColor;
		
		return *this;
	}

	filesystem::String faceFile;
	float size;
	Color color;
	bool outline;
	float outlineWidth;
	Color outlineColor;
	bool shadow;
	TrueText::Point shadowOffset;
	Color shadowColor;
};

TrueType::TrueType(resource::Resource* resource, const filesystem::String & name)
	: info(new FontInfo())
{
	if( !xml::StreamResource( resource ).load( &xml::Root(this, "TrueTypeFont") ) )
		THROW(GluckBadResource( __FUNCTION__, "load() failed", name));
}
	
TrueType::TrueType(const TrueType & other) : info(new FontInfo(*other.info.get())), font(new TrueText::Font(*other.font.get()))
{
}

TrueType::~TrueType()
{
}

void TrueType::Accept(FontVisitor* visitor)
{
	visitor->Visit(this);
}
	
bool TrueType::on_attribute( xml::Provider * prov, const std::wstring & name, const std::wstring & value )
{
	if (name == L"Size")
	{
		float size;
		if (converter::convert(value, &size))
		{
			info->size = size;
			return true;
		}
		return false;
	}
	else if (name == L"Face")
	{
		info->faceFile = value.c_str();
		return true;
	}
	else if (name == L"Color")
	{
		Color color;
		if (converter::convert(value, &color))
		{
			info->color = color;
			return true;
		}
		return false;
	}
	else if (name == L"Outline")
	{
		bool outline;
		if (converter::convert(value, &outline))
		{
			info->outline = outline;
			return true;
		}
	}
	else if (name == L"OutlineWidth")
	{
		float outlineWidth;
		if (converter::convert(value, &outlineWidth))
		{
			info->outlineWidth = outlineWidth;
			return true;
		}
	}
	else if (name == L"OutlineColor")
	{
		Color outlineColor;
		if (converter::convert(value, &outlineColor))
		{
			info->outlineColor = outlineColor;
			return true;
		}
	}
	else if(name == L"Shadow")
	{
		bool shadow;
		if (converter::convert(value, &shadow))
		{
			info->shadow = shadow;
			return true;
		}
	}
	else if (name == L"ShadowColor")
	{
		Color shadowColor;
		if (converter::convert(value, &shadowColor))
		{
			info->shadowColor = shadowColor;
			return true;
		}
	}
	else if (name == L"ShadowOffset")
	{
		math::Vector2<float> shadowOffset;
		if (converter::convert(value, &shadowOffset))
		{
			info->shadowOffset = TrueText::Point(
				static_cast<int>(shadowOffset.x),
				static_cast<int>(shadowOffset.y));
			return true;
		}
	}

	return false;
}

bool TrueType::on_end_node( xml::Provider * prov )
{
	return createFont();
}

TrueText::Color Color2TrueText(const Color& c)
{
	return TrueText::Color(255, (int)(255.0 * c.r), (int)(255.0 * c.g), (int)(255.0 * c.b));
}

namespace {
boost::shared_ptr<TrueText::Stream> faceReader(filesystem::String faceFile)
{
	std::auto_ptr<resource::Resource> res(resource::Manager::create_resource(faceFile));
	if(!res.get())
		THROW(GluckBadResource(__FUNCTION__, "Failed to load TrueType font face.", faceFile));

	if (!res->is_valid())
		THROW(GluckBadResource(__FUNCTION__, "Failed to load TrueType font face.", faceFile));

	std::vector<char> fontData;
	size_t resSize = res->get_size();
	fontData.resize(resSize);
	res->read(&fontData[0], resSize);

	boost::shared_ptr<TrueText::Stream> stream(new TrueText::CachingMemoryStream(&fontData[0], resSize));
	return stream;
}

} // anonymous namespace 

bool TrueType::createFont()
{
	try
	{
		TrueText::Face face(info->faceFile.c_str(), boost::bind(&faceReader, info->faceFile));
		
		std::auto_ptr<TrueText::Font> font(new TrueText::Font(face));
		font->SetHeight(info->size);
		font->SetColor(Color2TrueText(info->color));

		if (info->outline)
		{
			font->Outline().Enable(true);

			TrueText::OutlineInfo oi;
			oi.SetColor(Color2TrueText(info->outlineColor));
			oi.SetSize(info->outlineWidth);

			font->Outline().SetOutlineInfo(oi);
		}
		
		if(info->shadow)
		{
			font->Shadow().Enable(true);
			
			TrueText::ShadowInfo shadowInfo;
			shadowInfo.SetOffset(info->shadowOffset);
			shadowInfo.SetColor(Color2TrueText(info->shadowColor));
			
			font->Shadow().SetShadowInfo(shadowInfo);
		}
		
		this->font = font;

		return true;
	}
	catch (const TrueText::Error&)
	{
		return false;
	}
}

TrueText::Font* TrueType::Font()
{
	return font.get();
}

} // namespace font
} // namespace libao